﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;

namespace DbService
{
    using System.Data;
    using System.Data.Common;
    using System.Data.SqlClient;
    using System.Data.Entity;
    using System.Data.Entity.Infrastructure;
    using System.Linq.Expressions;
    using EntityFramework.Extensions;
    using DbService.Repository;
    using Common;

    public class UnitOfWorkAchieve : IUnitOfWork
    {
        protected DbContext Context;

        public UnitOfWorkAchieve(DbContext _DbContext)
        {
            Context = _DbContext;
        }

        /// <summary>
        /// Insert
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="_Entity"></param>
        public void Insert<T>(T _Entity) where T : class
        {
            this.CheckedSysNumberAttribute(_Entity);
            this.Context.Set<T>().Add(_Entity);
            this.Save();
        }

        /// <summary>
        /// InsertBatch
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="_Entity"></param>
        public void InsertBatch<T>(T[] _Entity) where T : class
        {
            foreach (var item in _Entity)
            {
                this.CheckedSysNumberAttribute(item);
            }
            this.Context.Set<T>().AddRange(_Entity);
            this.Save();
        }

        /// <summary>
        /// Update
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="_Entity"></param>
        /// <param name="_Where"></param>
        public void Update<T>(Expression<Func<T, T>> _Entity, Expression<Func<T, bool>> _Where) where T : class
        {
            this.Context.Set<T>().Where(_Where).Update(_Entity);
        }

        /// <summary>
        /// Update
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="_Entity"></param>
        public void UpdateById<T>(T _Entity) where T : class
        {
            var list = new List<MemberBinding>();
            var _KeyName = string.Empty;
            object _KeyValue = null;

            //创建一个 Expression<Func<T, T>> 表达式
            var _Set_Parameter = Expression.Parameter(typeof(T), "orig");
            var _Tuple = RepositoryHelper.GetEntityKey<T>(_Entity, (item) =>
              {
                  list.Add(Expression.Bind(item, Expression.Constant(item.GetValue(_Entity), item.PropertyType)));
              });
            _KeyName = _Tuple.Item1;
            _KeyValue = _Tuple.Item2;
            var _Set_Body = Expression.MemberInit( // object initializer 
                Expression.New(typeof(T)), // ctor 
                list // property assignments 
            );

            var _Set = Expression.Lambda<Func<T, T>>(_Set_Body, _Set_Parameter);
            var _Where = RepositoryHelper.WhereById<T>(_KeyName, _KeyValue);
            this.Update<T>(_Set, _Where);
        }

        /// <summary>
        /// Delete
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="_Where"></param>
        public void Delete<T>(Expression<Func<T, bool>> _Where) where T : class
        {
            Context.Set<T>().Where(_Where).Delete();
        }

        /// <summary>
        /// Delete
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="_Entity"></param>
        public void Delete<T>(T _Entity) where T : class
        {
            Context.Set<T>().Remove(_Entity);
        }

        /// <summary>
        /// DeleteById
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="Id"></param>
        public void DeleteById<T>(object Id) where T : class
        {
            var _Entity = RepositoryHelper.CreateInstance<T>();
            var _Tuple = RepositoryHelper.GetEntityKey<T>(_Entity);
            var _Where = RepositoryHelper.WhereById<T>(_Tuple.Item1, Id);
            this.Delete<T>(_Where);
        }

        /// <summary>
        /// FindSingle
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="_Where"></param>
        /// <returns></returns>
        public T FindSingle<T>(Expression<Func<T, bool>> _Where) where T : class
        {
            var _Entity = Context.Set<T>().AsNoTracking().FirstOrDefault(_Where);
            if (_Entity == null) _Entity = RepositoryHelper.CreateInstance<T>();
            return _Entity;
        }

        /// <summary>
        /// FindSingleById
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="Id"></param>
        /// <returns></returns>
        public T FindSingleById<T>(object Id) where T : class
        {
            var _Entity = RepositoryHelper.CreateInstance<T>();
            var _Tuple = RepositoryHelper.GetEntityKey<T>(_Entity);
            var _Where = RepositoryHelper.WhereById<T>(_Tuple.Item1, Id);
            return this.FindSingle<T>(_Where);
        }

        /// <summary>
        /// IsExist
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="_Where"></param>
        /// <returns></returns>
        public bool IsExist<T>(Expression<Func<T, bool>> _Where) where T : class
        {
            return Context.Set<T>().Any(_Where);
        }

        /// <summary>
        /// GetCount
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="_Where"></param>
        /// <returns></returns>
        public int GetCount<T>(Expression<Func<T, bool>> _Where) where T : class
        {
            return Filter(_Where).Count();
        }

        /// <summary>
        /// Find
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="_Where"></param>
        /// <returns></returns>
        public IQueryable<T> Find<T>(Expression<Func<T, bool>> _Where = null) where T : class
        {
            return Filter(_Where);
        }

        /// <summary>
        /// ExecuteSql 执行 增 删 改
        /// </summary>
        /// <param name="SqlString"></param>
        /// <param name="_DbParameter"></param>
        /// <returns></returns>
        public int ExecuteNonQuery(string SqlString, DbParameter[] _DbParameter = null)
        {
            var Database = Context.Database;
            return _DbParameter == null ? Database.ExecuteSqlCommand(SqlString) : Database.ExecuteSqlCommand(SqlString, _DbParameter);
        }

        /// <summary>
        /// ExecuteScalar 得到单行单列
        /// </summary>
        /// <param name="SqlString"></param>
        /// <param name="_DbParameter"></param>
        /// <returns></returns>
        public object ExecuteScalar(string SqlString, DbParameter[] _DbParameter = null)
        {
            SqlConnection _SqlConnection = new System.Data.SqlClient.SqlConnection();
            _SqlConnection.ConnectionString = Context.Database.Connection.ConnectionString;
            if (_SqlConnection.State != ConnectionState.Open) _SqlConnection.Open();

            SqlCommand _SqlCommand = new SqlCommand();
            _SqlCommand.Connection = _SqlConnection;
            _SqlCommand.CommandText = SqlString;
            if (_DbParameter != null && _DbParameter.Length > 0)
            {
                foreach (var item in _DbParameter)
                {
                    _SqlCommand.Parameters.Add(item);
                }
            }
            var _Object = _SqlCommand.ExecuteScalar();
            _SqlConnection.Close();
            _SqlConnection.Dispose();
            return _Object;
        }

        /// <summary>
        /// SqlQuery
        /// </summary>
        /// <typeparam name="Result"></typeparam>
        /// <param name="SqlString"></param>
        /// <param name="_DbParameter"></param>
        /// <returns></returns>
        public DbRawSqlQuery<Result> SqlQuery<Result>(string SqlString, DbParameter[] _DbParameter = null) where Result : class
        {
            var Database = Context.Database;
            return _DbParameter == null ? Database.SqlQuery<Result>(SqlString) : Database.SqlQuery<Result>(SqlString, _DbParameter);
        }

        /// <summary>
        /// SqlQuery 得到 DataTable 对象
        /// </summary>
        /// <param name="SqlString"></param>
        /// <param name="_DbParameter"></param>
        /// <returns></returns>
        public DataTable SqlQuery(string SqlString, DbParameter[] _DbParameter = null)
        {
            var _DataSet = this.SqlQueryDataSet(SqlString, _DbParameter);
            if (_DataSet.Tables.Count > 0)
                return _DataSet.Tables[0];
            return new DataTable();
        }

        /// <summary>
        /// 得到 DataSet
        /// </summary>
        /// <param name="SqlString"></param>
        /// <param name="_DbParameter"></param>
        /// <param name="_Action"></param>
        /// <returns></returns>
        public DataSet SqlQueryDataSet(string SqlString, DbParameter[] _DbParameter = null, Action<DbCommand> _Action = null)
        {
            SqlConnection _SqlConnection = new System.Data.SqlClient.SqlConnection();
            _SqlConnection.ConnectionString = Context.Database.Connection.ConnectionString;
            if (_SqlConnection.State != ConnectionState.Open) _SqlConnection.Open();

            SqlCommand _SqlCommand = new SqlCommand();
            _SqlCommand.Connection = _SqlConnection;
            _SqlCommand.CommandText = SqlString;

            if (_DbParameter != null && _DbParameter.Length > 0)
            {
                foreach (var item in _DbParameter)
                {
                    _SqlCommand.Parameters.Add(item);
                }
            }

            _Action?.Invoke(_SqlCommand);

            SqlDataAdapter _SqlDataAdapter = new SqlDataAdapter(_SqlCommand);
            var _DataSet = new DataSet();
            _SqlDataAdapter.Fill(_DataSet);

            _SqlConnection.Close();//连接需要关闭
            _SqlConnection.Dispose();
            return _DataSet;
        }

        /// <summary>
        /// 分页查询 返回这样一个 匿名对象 Tuple<DataTable, object> 元组 1：表 2：总数
        /// </summary>
        /// <param name="SqlString"></param>
        /// <param name="Page"></param>
        /// <param name="Rows"></param>
        /// <param name="_DbParameter"></param>
        /// <returns></returns>
        public Tuple<DataTable, object> SqlQuery(string SqlString, int Page, int Rows, DbParameter[] _DbParameter = null)
        {
            _DbParameter.ToList().ForEach(item =>
            {
                SqlString = SqlString.Replace("@" + item.ParameterName, item.Value == null ? null : "'" + item.Value + "' ");
            });

            var paramArray = new List<DbParameter>();
            paramArray.Add(new SqlParameter("@SQL", SqlString) { DbType = DbType.String, Direction = ParameterDirection.Input });
            paramArray.Add(new SqlParameter("@PAGE", Page) { DbType = DbType.Int32, Direction = ParameterDirection.Input });
            paramArray.Add(new SqlParameter("@PAGESIZE", Rows) { DbType = DbType.Int32, Direction = ParameterDirection.Input });
            paramArray.Add(new SqlParameter("@PAGECOUNT", Rows) { DbType = DbType.Int32, Direction = ParameterDirection.Output });
            paramArray.Add(new SqlParameter("@RECORDCOUNT", Rows) { DbType = DbType.Int32, Direction = ParameterDirection.Output });

            var _DataSet = this.SqlQueryDataSet("PROC_SPLITPAGE", paramArray.ToArray(), (_SqlCommand) =>
              {
                  _SqlCommand.CommandType = CommandType.StoredProcedure;
              });

            if (_DataSet.Tables.Count >= 2)
            {
                var _DataTable = _DataSet.Tables[1];
                if (_DataTable.Columns.Contains("rowstat")) _DataTable.Columns.Remove("rowstat");
                return new Tuple<DataTable, object>(_DataTable, paramArray.FirstOrDefault(w => w.ParameterName == "@RECORDCOUNT").Value);
            }
            return new Tuple<DataTable, object>(new DataTable(), 0);
        }

        /// <summary>
        /// Disposes the current object
        /// </summary>
        //public virtual void Dispose()
        //{
        //    Dispose(true);
        //    GC.SuppressFinalize(this);
        //}

        /// <summary>
        /// Disposes all external resources.
        /// </summary>
        /// <param name="disposing">The dispose indicator.</param>
        //private void Dispose(bool disposing)
        //{
        //    if (!disposing) return;
        //    if (Context == null) return;

        //    Context.Dispose();
        //    Context = null;
        //}

        public int Save()
        {
            return Context.SaveChanges();
        }

        public void Commit(Action _Action)
        {
            var _BeginTransaction = Context.Database.BeginTransaction();

            try
            {
                //调用委托
                _Action?.Invoke();

                _BeginTransaction.Commit();
            }
            catch (Exception ex)
            {
                _BeginTransaction.Rollback();
                throw ex;
            }
        }

        private IQueryable<T> Filter<T>(Expression<Func<T, bool>> exp) where T : class
        {
            var dbSet = Context.Set<T>().AsNoTracking().AsQueryable();
            if (exp != null)
                dbSet = dbSet.Where(exp);
            return dbSet;
        }

        /// <summary>
        /// 检查 SysNumber 特性是否存在
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="_Entity"></param>
        /// <param name="_Action"></param>
        private void CheckedSysNumberAttribute<T>(T _Entity) where T : class
        {
            RepositoryHelper.CheckedSysNumberAttribute<T>(_Entity, (_PropertyInfo, _Length, _String) =>
            {
                var _Number = this.ExecuteScalar("SELECT ISNULL(MAX(CONVERT(INT," + _PropertyInfo.Name + ")),0) FROM " + typeof(T).Name).ToInt32() + 1;

                var _Number_Str = _Number.ToString().PadLeft(_Length, _String);

                if (_PropertyInfo.PropertyType == typeof(int) && _PropertyInfo.PropertyType == typeof(int?))
                    _PropertyInfo.SetValue(_Entity, _Number_Str.ToInt32());
                else
                    _PropertyInfo.SetValue(_Entity, _Number_Str);
            });
        }

    }
}
